/*
 * Support for SSH connection sharing, i.e. permitting one PuTTY to
 * open its own channels over the SSH session being run by another.
 */

/*
 * Discussion and technical documentation
 * ======================================
 *
 * The basic strategy for PuTTY's implementation of SSH connection
 * sharing is to have a single 'upstream' PuTTY process, which manages
 * the real SSH connection and all the cryptography, and then zero or
 * more 'downstream' PuTTYs, which never talk to the real host but
 * only talk to the upstream through local IPC (Unix-domain sockets or
 * Windows named pipes).
 *
 * The downstreams communicate with the upstream using a protocol
 * derived from SSH itself, which I'll document in detail below. In
 * brief, though: the downstream->upstream protocol uses a trivial
 * binary packet protocol (just length/type/data) to encapsulate
 * unencrypted SSH messages, and downstreams talk to the upstream more
 * or less as if it was an SSH server itself. (So downstreams can
 * themselves open multiple SSH channels, for example, by sending
 * multiple SSH2_MSG_CHANNEL_OPENs; they can send CHANNEL_REQUESTs of
 * their choice within each channel, and they handle their own
 * WINDOW_ADJUST messages.)
 *
 * The upstream would ideally handle these downstreams by just putting
 * their messages into the queue for proper SSH-2 encapsulation and
 * encryption and sending them straight on to the server. However,
 * that's not quite feasible as written, because client-side channel
 * IDs could easily conflict (between multiple downstreams, or between
 * a downstream and the upstream). To protect against that, the
 * upstream rewrites the client-side channel IDs in messages it passes
 * on to the server, so that it's performing what you might describe
 * as 'channel-number NAT'. Then the upstream remembers which of its
 * own channel IDs are channels it's managing itself, and which are
 * placeholders associated with a particular downstream, so that when
 * replies come in from the server they can be sent on to the relevant
 * downstream (after un-NATting the channel number, of course).
 *
 * Global requests from downstreams are only accepted if the upstream
 * knows what to do about them; currently the only such requests are
 * the ones having to do with remote-to-local port forwarding (in
 * which, again, the upstream remembers that some of the forwardings
 * it's asked the server to set up were on behalf of particular
 * downstreams, and sends the incoming CHANNEL_OPENs to those
 * downstreams when connections come in).
 *
 * Other fiddly pieces of this mechanism are X forwarding and
 * (OpenSSH-style) agent forwarding. Both of these have a fundamental
 * problem arising from the protocol design: that the CHANNEL_OPEN
 * from the server introducing a forwarded connection does not carry
 * any indication of which session channel gave rise to it; so if
 * session channels from multiple downstreams enable those forwarding
 * methods, it's hard for the upstream to know which downstream to
 * send the resulting connections back to.
 *
 * For X forwarding, we can work around this in a really painful way
 * by using the fake X11 authorisation data sent to the server as part
 * of the forwarding setup: upstream ensures that every X forwarding
 * request carries distinguishable fake auth data, and then when X
 * connections come in it waits to see the auth data in the X11 setup
 * message before it decides which downstream to pass the connection
 * on to.
 *
 * For agent forwarding, that workaround is unavailable. As a result,
 * this system (and, as far as I can think of, any other system too)
 * has the fundamental constraint that it can only forward one SSH
 * agent - it can't forward two agents to different session channels.
 * So downstreams can request agent forwarding if they like, but if
 * they do, they'll get whatever SSH agent is known to the upstream
 * (if any) forwarded to their sessions.
 *
 * Downstream-to-upstream protocol
 * -------------------------------
 *
 * Here I document in detail the protocol spoken between PuTTY
 * downstreams and upstreams over local IPC. The IPC mechanism can
 * vary between host platforms, but the protocol is the same.
 *
 * The protocol commences with a version exchange which is exactly
 * like the SSH-2 one, in that each side sends a single line of text
 * of the form
 *
 *   <protocol>-<version>-<softwareversion> [comments] \r\n
 *
 * The only difference is that in real SSH-2, <protocol> is the string
 * "SSH", whereas in this protocol the string is
 * "SSHCONNECTION@putty.projects.tartarus.org".
 *
 * (The SSH RFCs allow many protocol-level identifier namespaces to be
 * extended by implementors without central standardisation as long as
 * they suffix "@" and a domain name they control to their new ids.
 * RFC 4253 does not define this particular name to be changeable at
 * all, but I like to think this is obviously how it would have done
 * so if the working group had foreseen the need :-)
 *
 * Thereafter, all data exchanged consists of a sequence of binary
 * packets concatenated end-to-end, each of which is of the form
 *
 *     uint32     length of packet, N
 *     byte[N]    N bytes of packet data
 *
 * and, since these are SSH-2 messages, the first data byte is taken
 * to be the packet type code.
 *
 * These messages are interpreted as those of an SSH connection, after
 * userauth completes, and without any repeat key exchange.
 * Specifically, any message from the SSH Connection Protocol is
 * permitted, and also SSH_MSG_IGNORE, SSH_MSG_DEBUG,
 * SSH_MSG_DISCONNECT and SSH_MSG_UNIMPLEMENTED from the SSH Transport
 * Protocol.
 *
 * This protocol imposes a few additional requirements, over and above
 * those of the standard SSH Connection Protocol:
 *
 * Message sizes are not permitted to exceed 0x4010 (16400) bytes,
 * including their length header.
 *
 * When the server (i.e. really the PuTTY upstream) sends
 * SSH_MSG_CHANNEL_OPEN with channel type "x11", and the client
 * (downstream) responds with SSH_MSG_CHANNEL_OPEN_CONFIRMATION, that
 * confirmation message MUST include an initial window size of at
 * least 256. (Rationale: this is a bit of a fudge which makes it
 * easier, by eliminating the possibility of nasty edge cases, for an
 * upstream to arrange not to pass the CHANNEL_OPEN on to downstream
 * until after it's seen the X11 auth data to decide which downstream
 * it needs to go to.)
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#include <errno.h>

#include "putty.h"
#include "tree234.h"
#include "ssh.h"

struct ssh_sharing_state {
  const struct plug_function_table *fn;
  /* the above variable absolutely *must* be the first in this structure */

  char *sockname;         /* the socket name, kept for cleanup */
  Socket listensock;      /* the master listening Socket */
  tree234 *connections;   /* holds ssh_sharing_connstates */
  unsigned nextid;        /* preferred id for next connstate */
  Ssh ssh;                /* instance of the ssh backend */
  char *server_verstring; /* server version string after "SSH-" */
};

struct share_globreq;

struct ssh_sharing_connstate {
  const struct plug_function_table *fn;
  /* the above variable absolutely *must* be the first in this structure */

  unsigned id; /* used to identify this downstream in log messages */

  Socket sock; /* the Socket for this connection */
  struct ssh_sharing_state *parent;

  int crLine; /* coroutine state for share_receive */

  int sent_verstring, got_verstring, curr_packetlen;

  unsigned char recvbuf[0x4010];
  int recvlen;

  /*
   * Assorted state we have to remember about this downstream, so
   * that we can clean it up appropriately when the downstream goes
   * away.
   */

  /* Channels which don't have a downstream id, i.e. we've passed a
   * CHANNEL_OPEN down from the server but not had an
   * OPEN_CONFIRMATION or OPEN_FAILURE back. If downstream goes
   * away, we respond to all of these with OPEN_FAILURE. */
  tree234 *halfchannels; /* stores 'struct share_halfchannel' */

  /* Channels which do have a downstream id. We need to index these
   * by both server id and upstream id, so we can find a channel
   * when handling either an upward or a downward message referring
   * to it. */
  tree234 *channels_by_us;     /* stores 'struct share_channel' */
  tree234 *channels_by_server; /* stores 'struct share_channel' */

  /* Another class of channel which doesn't have a downstream id.
   * The difference between these and halfchannels is that xchannels
   * do have an *upstream* id, because upstream has already accepted
   * the channel request from the server. This arises in the case of
   * X forwarding, where we have to accept the request and read the
   * X authorisation data before we know whether the channel needs
   * to be forwarded to a downstream. */
  tree234 *xchannels_by_us;     /* stores 'struct share_xchannel' */
  tree234 *xchannels_by_server; /* stores 'struct share_xchannel' */

  /* Remote port forwarding requests in force. */
  tree234 *forwardings; /* stores 'struct share_forwarding' */

  /* Global requests we've sent on to the server, pending replies. */
  struct share_globreq *globreq_head, *globreq_tail;
};

struct share_halfchannel {
  unsigned server_id;
};

/* States of a share_channel. */
enum
{
  OPEN,
  SENT_CLOSE,
  RCVD_CLOSE,
  /* Downstream has sent CHANNEL_OPEN but server hasn't replied yet.
   * If downstream goes away when a channel is in this state, we
   * must wait for the server's response before starting to send
   * CLOSE. Channels in this state are also not held in
   * channels_by_server, because their server_id field is
   * meaningless. */
  UNACKNOWLEDGED
};

struct share_channel {
  unsigned downstream_id, upstream_id, server_id;
  int downstream_maxpkt;
  int state;
  /*
   * Some channels (specifically, channels on which downstream has
   * sent "x11-req") have the additional function of storing a set
   * of downstream X authorisation data and a handle to an upstream
   * fake set.
   */
  struct X11FakeAuth *x11_auth_upstream;
  int x11_auth_proto;
  char *x11_auth_data;
  int x11_auth_datalen;
  int x11_one_shot;
};

struct share_forwarding {
  char *host;
  int port;
  int active; /* has the server sent REQUEST_SUCCESS? */
};

struct share_xchannel_message {
  struct share_xchannel_message *next;
  int type;
  unsigned char *data;
  int datalen;
};

struct share_xchannel {
  unsigned upstream_id, server_id;

  /*
   * xchannels come in two flavours: live and dead. Live ones are
   * waiting for an OPEN_CONFIRMATION or OPEN_FAILURE from
   * downstream; dead ones have had an OPEN_FAILURE, so they only
   * exist as a means of letting us conveniently respond to further
   * channel messages from the server until such time as the server
   * sends us CHANNEL_CLOSE.
   */
  int live;

  /*
   * When we receive OPEN_CONFIRMATION, we will need to send a
   * WINDOW_ADJUST to the server to synchronise the windows. For
   * this purpose we need to know what window we have so far offered
   * the server. We record this as exactly the value in the
   * OPEN_CONFIRMATION that upstream sent us, adjusted by the amount
   * by which the two X greetings differed in length.
   */
  int window;

  /*
   * Linked list of SSH messages from the server relating to this
   * channel, which we queue up until downstream sends us an
   * OPEN_CONFIRMATION and we can belatedly send them all on.
   */
  struct share_xchannel_message *msghead, *msgtail;
};

enum
{
  GLOBREQ_TCPIP_FORWARD,
  GLOBREQ_CANCEL_TCPIP_FORWARD
};

struct share_globreq {
  struct share_globreq *next;
  int type;
  int want_reply;
  struct share_forwarding *fwd;
};

static int share_connstate_cmp(void *av, void *bv)
{
  const struct ssh_sharing_connstate *a =
      (const struct ssh_sharing_connstate *)av;
  const struct ssh_sharing_connstate *b =
      (const struct ssh_sharing_connstate *)bv;

  if (a->id < b->id)
    return -1;
  else if (a->id > b->id)
    return +1;
  else
    return 0;
}

static unsigned share_find_unused_id(struct ssh_sharing_state *sharestate,
                                     unsigned first)
{
  int low_orig, low, mid, high, high_orig;
  struct ssh_sharing_connstate *cs;
  unsigned ret;

  /*
   * Find the lowest unused downstream ID greater or equal to
   * 'first'.
   *
   * Begin by seeing if 'first' itself is available. If it is, we'll
   * just return it; if it's already in the tree, we'll find the
   * tree index where it appears and use that for the next stage.
   */
  {
    struct ssh_sharing_connstate dummy;
    dummy.id = first;
    cs = findrelpos234(
        sharestate->connections, &dummy, NULL, REL234_GE, &low_orig);
    if (!cs)
      return first;
  }

  /*
   * Now binary-search using the counted B-tree, to find the largest
   * ID which is in a contiguous sequence from the beginning of that
   * range.
   */
  low = low_orig;
  high = high_orig = count234(sharestate->connections);
  while (high - low > 1) {
    mid = (high + low) / 2;
    cs = index234(sharestate->connections, mid);
    if (cs->id == first + (mid - low_orig))
      low = mid; /* this one is still in the sequence */
    else
      high = mid; /* this one is past the end */
  }

  /*
   * Now low is the tree index of the largest ID in the initial
   * sequence. So the return value is one more than low's id, and we
   * know low's id is given by the formula in the binary search loop
   * above.
   *
   * (If an SSH connection went on for _enormously_ long, we might
   * reach a point where all ids from 'first' to UINT_MAX were in
   * use. In that situation the formula below would wrap round by
   * one and return zero, which is conveniently the right way to
   * signal 'no id available' from this function.)
   */
  ret = first + (low - low_orig) + 1;
  {
    struct ssh_sharing_connstate dummy;
    dummy.id = ret;
    assert(NULL == find234(sharestate->connections, &dummy, NULL));
  }
  return ret;
}

static int share_halfchannel_cmp(void *av, void *bv)
{
  const struct share_halfchannel *a = (const struct share_halfchannel *)av;
  const struct share_halfchannel *b = (const struct share_halfchannel *)bv;

  if (a->server_id < b->server_id)
    return -1;
  else if (a->server_id > b->server_id)
    return +1;
  else
    return 0;
}

static int share_channel_us_cmp(void *av, void *bv)
{
  const struct share_channel *a = (const struct share_channel *)av;
  const struct share_channel *b = (const struct share_channel *)bv;

  if (a->upstream_id < b->upstream_id)
    return -1;
  else if (a->upstream_id > b->upstream_id)
    return +1;
  else
    return 0;
}

static int share_channel_server_cmp(void *av, void *bv)
{
  const struct share_channel *a = (const struct share_channel *)av;
  const struct share_channel *b = (const struct share_channel *)bv;

  if (a->server_id < b->server_id)
    return -1;
  else if (a->server_id > b->server_id)
    return +1;
  else
    return 0;
}

static int share_xchannel_us_cmp(void *av, void *bv)
{
  const struct share_xchannel *a = (const struct share_xchannel *)av;
  const struct share_xchannel *b = (const struct share_xchannel *)bv;

  if (a->upstream_id < b->upstream_id)
    return -1;
  else if (a->upstream_id > b->upstream_id)
    return +1;
  else
    return 0;
}

static int share_xchannel_server_cmp(void *av, void *bv)
{
  const struct share_xchannel *a = (const struct share_xchannel *)av;
  const struct share_xchannel *b = (const struct share_xchannel *)bv;

  if (a->server_id < b->server_id)
    return -1;
  else if (a->server_id > b->server_id)
    return +1;
  else
    return 0;
}

static int share_forwarding_cmp(void *av, void *bv)
{
  const struct share_forwarding *a = (const struct share_forwarding *)av;
  const struct share_forwarding *b = (const struct share_forwarding *)bv;
  int i;

  if ((i = strcmp(a->host, b->host)) != 0)
    return i;
  else if (a->port < b->port)
    return -1;
  else if (a->port > b->port)
    return +1;
  else
    return 0;
}

static void share_xchannel_free(struct share_xchannel *xc)
{
  while (xc->msghead) {
    struct share_xchannel_message *tmp = xc->msghead;
    xc->msghead = tmp->next;
    sfree(tmp);
  }
  sfree(xc);
}

static void share_connstate_free(struct ssh_sharing_connstate *cs)
{
  struct share_halfchannel *hc;
  struct share_xchannel *xc;
  struct share_channel *chan;
  struct share_forwarding *fwd;

  while ((hc = (struct share_halfchannel *)delpos234(cs->halfchannels, 0)) !=
         NULL)
    sfree(hc);
  freetree234(cs->halfchannels);

  /* All channels live in 'channels_by_us' but only some in
   * 'channels_by_server', so we use the former to find the list of
   * ones to free */
  freetree234(cs->channels_by_server);
  while ((chan = (struct share_channel *)delpos234(cs->channels_by_us, 0)) !=
         NULL)
    sfree(chan);
  freetree234(cs->channels_by_us);

  /* But every xchannel is in both trees, so it doesn't matter which
   * we use to free them. */
  while ((xc = (struct share_xchannel *)delpos234(cs->xchannels_by_us, 0)) !=
         NULL)
    share_xchannel_free(xc);
  freetree234(cs->xchannels_by_us);
  freetree234(cs->xchannels_by_server);

  while ((fwd = (struct share_forwarding *)delpos234(cs->forwardings, 0)) !=
         NULL)
    sfree(fwd);
  freetree234(cs->forwardings);

  while (cs->globreq_head) {
    struct share_globreq *globreq = cs->globreq_head;
    cs->globreq_head = cs->globreq_head->next;
    sfree(globreq);
  }

  if (cs->sock)
    sk_close(cs->sock);

  sfree(cs);
}

void sharestate_free(void *v)
{
  struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)v;
  struct ssh_sharing_connstate *cs;

  platform_ssh_share_cleanup(sharestate->sockname);

  while ((cs = (struct ssh_sharing_connstate *)delpos234(
              sharestate->connections, 0)) != NULL) {
    share_connstate_free(cs);
  }
  freetree234(sharestate->connections);
  if (sharestate->listensock) {
    sk_close(sharestate->listensock);
    sharestate->listensock = NULL;
  }
  sfree(sharestate->server_verstring);
  sfree(sharestate->sockname);
  sfree(sharestate);
}

static struct share_halfchannel *share_add_halfchannel(
    struct ssh_sharing_connstate *cs, unsigned server_id)
{
  struct share_halfchannel *hc = snew(struct share_halfchannel);
  hc->server_id = server_id;
  if (add234(cs->halfchannels, hc) != hc) {
    /* Duplicate?! */
    sfree(hc);
    return NULL;
  } else {
    return hc;
  }
}

static struct share_halfchannel *share_find_halfchannel(
    struct ssh_sharing_connstate *cs, unsigned server_id)
{
  struct share_halfchannel dummyhc;
  dummyhc.server_id = server_id;
  return find234(cs->halfchannels, &dummyhc, NULL);
}

static void share_remove_halfchannel(struct ssh_sharing_connstate *cs,
                                     struct share_halfchannel *hc)
{
  del234(cs->halfchannels, hc);
  sfree(hc);
}

static struct share_channel *share_add_channel(struct ssh_sharing_connstate *cs,
                                               unsigned downstream_id,
                                               unsigned upstream_id,
                                               unsigned server_id,
                                               int state,
                                               int maxpkt)
{
  struct share_channel *chan = snew(struct share_channel);
  chan->downstream_id = downstream_id;
  chan->upstream_id = upstream_id;
  chan->server_id = server_id;
  chan->state = state;
  chan->downstream_maxpkt = maxpkt;
  chan->x11_auth_upstream = NULL;
  chan->x11_auth_data = NULL;
  chan->x11_auth_proto = -1;
  chan->x11_auth_datalen = 0;
  chan->x11_one_shot = 0;
  if (add234(cs->channels_by_us, chan) != chan) {
    sfree(chan);
    return NULL;
  }
  if (chan->state != UNACKNOWLEDGED) {
    if (add234(cs->channels_by_server, chan) != chan) {
      del234(cs->channels_by_us, chan);
      sfree(chan);
      return NULL;
    }
  }
  return chan;
}

static void share_channel_set_server_id(struct ssh_sharing_connstate *cs,
                                        struct share_channel *chan,
                                        unsigned server_id,
                                        int newstate)
{
  chan->server_id = server_id;
  chan->state = newstate;
  assert(newstate != UNACKNOWLEDGED);
  add234(cs->channels_by_server, chan);
}

static struct share_channel *share_find_channel_by_upstream(
    struct ssh_sharing_connstate *cs, unsigned upstream_id)
{
  struct share_channel dummychan;
  dummychan.upstream_id = upstream_id;
  return find234(cs->channels_by_us, &dummychan, NULL);
}

static struct share_channel *share_find_channel_by_server(
    struct ssh_sharing_connstate *cs, unsigned server_id)
{
  struct share_channel dummychan;
  dummychan.server_id = server_id;
  return find234(cs->channels_by_server, &dummychan, NULL);
}

static void share_remove_channel(struct ssh_sharing_connstate *cs,
                                 struct share_channel *chan)
{
  del234(cs->channels_by_us, chan);
  del234(cs->channels_by_server, chan);
  if (chan->x11_auth_upstream)
    ssh_sharing_remove_x11_display(cs->parent->ssh, chan->x11_auth_upstream);
  sfree(chan->x11_auth_data);
  sfree(chan);
}

static struct share_xchannel *share_add_xchannel(
    struct ssh_sharing_connstate *cs, unsigned upstream_id, unsigned server_id)
{
  struct share_xchannel *xc = snew(struct share_xchannel);
  xc->upstream_id = upstream_id;
  xc->server_id = server_id;
  xc->live = TRUE;
  xc->msghead = xc->msgtail = NULL;
  if (add234(cs->xchannels_by_us, xc) != xc) {
    sfree(xc);
    return NULL;
  }
  if (add234(cs->xchannels_by_server, xc) != xc) {
    del234(cs->xchannels_by_us, xc);
    sfree(xc);
    return NULL;
  }
  return xc;
}

static struct share_xchannel *share_find_xchannel_by_upstream(
    struct ssh_sharing_connstate *cs, unsigned upstream_id)
{
  struct share_xchannel dummyxc;
  dummyxc.upstream_id = upstream_id;
  return find234(cs->xchannels_by_us, &dummyxc, NULL);
}

static struct share_xchannel *share_find_xchannel_by_server(
    struct ssh_sharing_connstate *cs, unsigned server_id)
{
  struct share_xchannel dummyxc;
  dummyxc.server_id = server_id;
  return find234(cs->xchannels_by_server, &dummyxc, NULL);
}

static void share_remove_xchannel(struct ssh_sharing_connstate *cs,
                                  struct share_xchannel *xc)
{
  del234(cs->xchannels_by_us, xc);
  del234(cs->xchannels_by_server, xc);
  share_xchannel_free(xc);
}

static struct share_forwarding *share_add_forwarding(
    struct ssh_sharing_connstate *cs, const char *host, int port)
{
  struct share_forwarding *fwd = snew(struct share_forwarding);
  fwd->host = dupstr(host);
  fwd->port = port;
  fwd->active = FALSE;
  if (add234(cs->forwardings, fwd) != fwd) {
    /* Duplicate?! */
    sfree(fwd);
    return NULL;
  }
  return fwd;
}

static struct share_forwarding *share_find_forwarding(
    struct ssh_sharing_connstate *cs, const char *host, int port)
{
  struct share_forwarding dummyfwd, *ret;
  dummyfwd.host = dupstr(host);
  dummyfwd.port = port;
  ret = find234(cs->forwardings, &dummyfwd, NULL);
  sfree(dummyfwd.host);
  return ret;
}

static void share_remove_forwarding(struct ssh_sharing_connstate *cs,
                                    struct share_forwarding *fwd)
{
  del234(cs->forwardings, fwd);
  sfree(fwd);
}

static void send_packet_to_downstream(struct ssh_sharing_connstate *cs,
                                      int type,
                                      const void *pkt,
                                      int pktlen,
                                      struct share_channel *chan)
{
  if (!cs->sock) /* throw away all packets destined for a dead downstream */
    return;

  if (type == SSH2_MSG_CHANNEL_DATA) {
    /*
     * Special case which we take care of at a low level, so as to
     * be sure to apply it in all cases. On rare occasions we
     * might find that we have a channel for which the
     * downstream's maximum packet size exceeds the max packet
     * size we presented to the server on its behalf. (This can
     * occur in X11 forwarding, where we have to send _our_
     * CHANNEL_OPEN_CONFIRMATION before we discover which if any
     * downstream the channel is destined for, so if that
     * downstream turns out to present a smaller max packet size
     * then we're in this situation.)
     *
     * If that happens, we just chop up the packet into pieces and
     * send them as separate CHANNEL_DATA packets.
     */
    const char *upkt = (const char *)pkt;
    char header[13]; /* 4 length + 1 type + 4 channel id + 4 string len */

    int len = toint(GET_32BIT(upkt + 4));
    upkt += 8; /* skip channel id + length field */

    if (len < 0 || len > pktlen - 8)
      len = pktlen - 8;

    do {
      int this_len =
          (len > chan->downstream_maxpkt ? chan->downstream_maxpkt : len);
      PUT_32BIT(header, this_len + 9);
      header[4] = type;
      PUT_32BIT(header + 5, chan->downstream_id);
      PUT_32BIT(header + 9, this_len);
      sk_write(cs->sock, header, 13);
      sk_write(cs->sock, upkt, this_len);
      len -= this_len;
      upkt += this_len;
    } while (len > 0);
  } else {
    /*
     * Just do the obvious thing.
     */
    char header[9];

    PUT_32BIT(header, pktlen + 1);
    header[4] = type;
    sk_write(cs->sock, header, 5);
    sk_write(cs->sock, pkt, pktlen);
  }
}

static void share_try_cleanup(struct ssh_sharing_connstate *cs)
{
  int i;
  struct share_halfchannel *hc;
  struct share_channel *chan;
  struct share_forwarding *fwd;

  /*
   * Any half-open channels, i.e. those for which we'd received
   * CHANNEL_OPEN from the server but not passed back a response
   * from downstream, should be responded to with OPEN_FAILURE.
   */
  while ((hc = (struct share_halfchannel *)index234(cs->halfchannels, 0)) !=
         NULL) {
    static const char reason[] = "PuTTY downstream no longer available";
    static const char lang[] = "en";
    unsigned char packet[256];
    int pos = 0;

    PUT_32BIT(packet + pos, hc->server_id);
    pos += 4;
    PUT_32BIT(packet + pos, SSH2_OPEN_CONNECT_FAILED);
    pos += 4;
    PUT_32BIT(packet + pos, strlen(reason));
    pos += 4;
    memcpy(packet + pos, reason, strlen(reason));
    pos += strlen(reason);
    PUT_32BIT(packet + pos, strlen(lang));
    pos += 4;
    memcpy(packet + pos, lang, strlen(lang));
    pos += strlen(lang);
    ssh_send_packet_from_downstream(cs->parent->ssh,
                                    cs->id,
                                    SSH2_MSG_CHANNEL_OPEN_FAILURE,
                                    packet,
                                    pos,
                                    "cleanup after"
                                    " downstream went away");

    share_remove_halfchannel(cs, hc);
  }

  /*
   * Any actually open channels should have a CHANNEL_CLOSE sent for
   * them, unless we've already done so. We won't be able to
   * actually clean them up until CHANNEL_CLOSE comes back from the
   * server, though (unless the server happens to have sent a CLOSE
   * already).
   *
   * Another annoying exception is UNACKNOWLEDGED channels, i.e.
   * we've _sent_ a CHANNEL_OPEN to the server but not received an
   * OPEN_CONFIRMATION or OPEN_FAILURE. We must wait for a reply
   * before closing the channel, because until we see that reply we
   * won't have the server's channel id to put in the close message.
   */
  for (i = 0;
       (chan = (struct share_channel *)index234(cs->channels_by_us, i)) != NULL;
       i++) {
    unsigned char packet[256];
    int pos = 0;

    if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) {
      PUT_32BIT(packet + pos, chan->server_id);
      pos += 4;
      ssh_send_packet_from_downstream(cs->parent->ssh,
                                      cs->id,
                                      SSH2_MSG_CHANNEL_CLOSE,
                                      packet,
                                      pos,
                                      "cleanup after"
                                      " downstream went away");
      if (chan->state != RCVD_CLOSE) {
        chan->state = SENT_CLOSE;
      } else {
        /* In this case, we _can_ clear up the channel now. */
        ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id);
        share_remove_channel(cs, chan);
        i--; /* don't accidentally skip one as a result */
      }
    }
  }

  /*
   * Any remote port forwardings we're managing on behalf of this
   * downstream should be cancelled. Again, we must defer those for
   * which we haven't yet seen REQUEST_SUCCESS/FAILURE.
   *
   * We take a fire-and-forget approach during cleanup, not
   * bothering to set want_reply.
   */
  for (i = 0;
       (fwd = (struct share_forwarding *)index234(cs->forwardings, i)) != NULL;
       i++) {
    if (fwd->active) {
      static const char request[] = "cancel-tcpip-forward";
      char *packet = snewn(256 + strlen(fwd->host), char);
      int pos = 0;

      PUT_32BIT(packet + pos, strlen(request));
      pos += 4;
      memcpy(packet + pos, request, strlen(request));
      pos += strlen(request);

      packet[pos++] = 0; /* !want_reply */

      PUT_32BIT(packet + pos, strlen(fwd->host));
      pos += 4;
      memcpy(packet + pos, fwd->host, strlen(fwd->host));
      pos += strlen(fwd->host);

      PUT_32BIT(packet + pos, fwd->port);
      pos += 4;

      ssh_send_packet_from_downstream(cs->parent->ssh,
                                      cs->id,
                                      SSH2_MSG_GLOBAL_REQUEST,
                                      packet,
                                      pos,
                                      "cleanup after"
                                      " downstream went away");
      sfree(packet);

      share_remove_forwarding(cs, fwd);
      i--; /* don't accidentally skip one as a result */
    }
  }

  if (count234(cs->halfchannels) == 0 && count234(cs->channels_by_us) == 0 &&
      count234(cs->forwardings) == 0) {
    /*
     * Now we're _really_ done, so we can get rid of cs completely.
     */
    del234(cs->parent->connections, cs);
    ssh_sharing_downstream_disconnected(cs->parent->ssh, cs->id);
    share_connstate_free(cs);
  }
}

static void share_begin_cleanup(struct ssh_sharing_connstate *cs)
{

  sk_close(cs->sock);
  cs->sock = NULL;

  share_try_cleanup(cs);
}

static void share_disconnect(struct ssh_sharing_connstate *cs,
                             const char *message)
{
  static const char lang[] = "en";
  int msglen = strlen(message);
  char *packet = snewn(msglen + 256, char);
  int pos = 0;

  PUT_32BIT(packet + pos, SSH2_DISCONNECT_PROTOCOL_ERROR);
  pos += 4;

  PUT_32BIT(packet + pos, msglen);
  pos += 4;
  memcpy(packet + pos, message, msglen);
  pos += msglen;

  PUT_32BIT(packet + pos, strlen(lang));
  pos += 4;
  memcpy(packet + pos, lang, strlen(lang));
  pos += strlen(lang);

  send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, packet, pos, NULL);

  share_begin_cleanup(cs);
}

static void share_closing(Plug plug,
                          const char *error_msg,
                          int error_code,
                          int calling_back)
{
  struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug;

  if (error_msg) {
#ifdef BROKEN_PIPE_ERROR_CODE
    /*
     * Most of the time, we log what went wrong when a downstream
     * disappears with a socket error. One exception, though, is
     * receiving EPIPE when we haven't received a protocol version
     * string from the downstream, because that can happen as a result
     * of plink -shareexists (opening the connection and instantly
     * closing it again without bothering to read our version string).
     * So that one case is not treated as a log-worthy error.
     */
    if (error_code == BROKEN_PIPE_ERROR_CODE && !cs->got_verstring)
      /* do nothing */;
    else
#endif
      ssh_sharing_logf(cs->parent->ssh, cs->id, "Socket error: %s", error_msg);
  }
  share_begin_cleanup(cs);
}

static int getstring_inner(const void *vdata,
                           int datalen,
                           char **out,
                           int *outlen)
{
  const unsigned char *data = (const unsigned char *)vdata;
  int len;

  if (datalen < 4)
    return FALSE;

  len = toint(GET_32BIT(data));
  if (len < 0 || len > datalen - 4)
    return FALSE;

  if (outlen)
    *outlen = len + 4; /* total size including length field */
  if (out)
    *out = dupprintf("%.*s", len, (char *)data + 4);
  return TRUE;
}

static char *getstring(const void *data, int datalen)
{
  char *ret;
  if (getstring_inner(data, datalen, &ret, NULL))
    return ret;
  else
    return NULL;
}

static int getstring_size(const void *data, int datalen)
{
  int ret;
  if (getstring_inner(data, datalen, NULL, &ret))
    return ret;
  else
    return -1;
}

/*
 * Append a message to the end of an xchannel's queue, with the length
 * and type code filled in and the data block allocated but
 * uninitialised.
 */
struct share_xchannel_message *share_xchannel_add_message(
    struct share_xchannel *xc, int type, int len)
{
  unsigned char *block;
  struct share_xchannel_message *msg;

  /*
   * Be a little tricksy here by allocating a single memory block
   * containing both the 'struct share_xchannel_message' and the
   * actual data. Simplifies freeing it later.
   */
  block = smalloc(sizeof(struct share_xchannel_message) + len);
  msg = (struct share_xchannel_message *)block;
  msg->data = block + sizeof(struct share_xchannel_message);
  msg->datalen = len;
  msg->type = type;

  /*
   * Queue it in the xchannel.
   */
  if (xc->msgtail)
    xc->msgtail->next = msg;
  else
    xc->msghead = msg;
  msg->next = NULL;
  xc->msgtail = msg;

  return msg;
}

void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs,
                                 struct share_xchannel *xc)
{
  /*
   * Handle queued incoming messages from the server destined for an
   * xchannel which is dead (i.e. downstream sent OPEN_FAILURE).
   */
  int delete = FALSE;
  while (xc->msghead) {
    struct share_xchannel_message *msg = xc->msghead;
    xc->msghead = msg->next;

    if (msg->type == SSH2_MSG_CHANNEL_REQUEST && msg->datalen > 4) {
      /*
       * A CHANNEL_REQUEST is responded to by sending
       * CHANNEL_FAILURE, if it has want_reply set.
       */
      int wantreplypos = getstring_size(msg->data, msg->datalen);
      if (wantreplypos > 0 && wantreplypos < msg->datalen &&
          msg->data[wantreplypos] != 0) {
        unsigned char id[4];
        PUT_32BIT(id, xc->server_id);
        ssh_send_packet_from_downstream(cs->parent->ssh,
                                        cs->id,
                                        SSH2_MSG_CHANNEL_FAILURE,
                                        id,
                                        4,
                                        "downstream refused X channel open");
      }
    } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) {
      /*
       * On CHANNEL_CLOSE we can discard the channel completely.
       */
      delete = TRUE;
    }

    sfree(msg);
  }
  xc->msgtail = NULL;
  if (delete) {
    ssh_delete_sharing_channel(cs->parent->ssh, xc->upstream_id);
    share_remove_xchannel(cs, xc);
  }
}

void share_xchannel_confirmation(struct ssh_sharing_connstate *cs,
                                 struct share_xchannel *xc,
                                 struct share_channel *chan,
                                 unsigned downstream_window)
{
  unsigned char window_adjust[8];

  /*
   * Send all the queued messages downstream.
   */
  while (xc->msghead) {
    struct share_xchannel_message *msg = xc->msghead;
    xc->msghead = msg->next;

    if (msg->datalen >= 4)
      PUT_32BIT(msg->data, chan->downstream_id);
    send_packet_to_downstream(cs, msg->type, msg->data, msg->datalen, chan);

    sfree(msg);
  }

  /*
   * Send a WINDOW_ADJUST back upstream, to synchronise the window
   * size downstream thinks it's presented with the one we've
   * actually presented.
   */
  PUT_32BIT(window_adjust, xc->server_id);
  PUT_32BIT(window_adjust + 4, downstream_window - xc->window);
  ssh_send_packet_from_downstream(cs->parent->ssh,
                                  cs->id,
                                  SSH2_MSG_CHANNEL_WINDOW_ADJUST,
                                  window_adjust,
                                  8,
                                  "window adjustment after"
                                  " downstream accepted X channel");
}

void share_xchannel_failure(struct ssh_sharing_connstate *cs,
                            struct share_xchannel *xc)
{
  /*
   * If downstream refuses to open our X channel at all for some
   * reason, we must respond by sending an emergency CLOSE upstream.
   */
  unsigned char id[4];
  PUT_32BIT(id, xc->server_id);
  ssh_send_packet_from_downstream(cs->parent->ssh,
                                  cs->id,
                                  SSH2_MSG_CHANNEL_CLOSE,
                                  id,
                                  4,
                                  "downstream refused X channel open");

  /*
   * Now mark the xchannel as dead, and respond to anything sent on
   * it until we see CLOSE for it in turn.
   */
  xc->live = FALSE;
  share_dead_xchannel_respond(cs, xc);
}

void share_setup_x11_channel(void *csv,
                             void *chanv,
                             unsigned upstream_id,
                             unsigned server_id,
                             unsigned server_currwin,
                             unsigned server_maxpkt,
                             unsigned client_adjusted_window,
                             const char *peer_addr,
                             int peer_port,
                             int endian,
                             int protomajor,
                             int protominor,
                             const void *initial_data,
                             int initial_len)
{
  struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)csv;
  struct share_channel *chan = (struct share_channel *)chanv;
  struct share_xchannel *xc;
  struct share_xchannel_message *msg;
  void *greeting;
  int greeting_len;
  unsigned char *pkt;
  int pktlen;

  /*
   * Create an xchannel containing data we've already received from
   * the X client, and preload it with a CHANNEL_DATA message
   * containing our own made-up authorisation greeting and any
   * additional data sent from the server so far.
   */
  xc = share_add_xchannel(cs, upstream_id, server_id);
  greeting = x11_make_greeting(endian,
                               protomajor,
                               protominor,
                               chan->x11_auth_proto,
                               chan->x11_auth_data,
                               chan->x11_auth_datalen,
                               peer_addr,
                               peer_port,
                               &greeting_len);
  msg = share_xchannel_add_message(
      xc, SSH2_MSG_CHANNEL_DATA, 8 + greeting_len + initial_len);
  /* leave the channel id field unfilled - we don't know the
   * downstream id yet, of course */
  PUT_32BIT(msg->data + 4, greeting_len + initial_len);
  memcpy(msg->data + 8, greeting, greeting_len);
  memcpy(msg->data + 8 + greeting_len, initial_data, initial_len);
  sfree(greeting);

  xc->window = client_adjusted_window + greeting_len;

  /*
   * Send on a CHANNEL_OPEN to downstream.
   */
  pktlen = 27 + strlen(peer_addr);
  pkt = snewn(pktlen, unsigned char);
  PUT_32BIT(pkt, 3); /* strlen("x11") */
  memcpy(pkt + 4, "x11", 3);
  PUT_32BIT(pkt + 7, server_id);
  PUT_32BIT(pkt + 11, server_currwin);
  PUT_32BIT(pkt + 15, server_maxpkt);
  PUT_32BIT(pkt + 19, strlen(peer_addr));
  memcpy(pkt + 23, peer_addr, strlen(peer_addr));
  PUT_32BIT(pkt + 23 + strlen(peer_addr), peer_port);
  send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, pkt, pktlen, NULL);
  sfree(pkt);

  /*
   * If this was a once-only X forwarding, clean it up now.
   */
  if (chan->x11_one_shot) {
    ssh_sharing_remove_x11_display(cs->parent->ssh, chan->x11_auth_upstream);
    chan->x11_auth_upstream = NULL;
    sfree(chan->x11_auth_data);
    chan->x11_auth_proto = -1;
    chan->x11_auth_datalen = 0;
    chan->x11_one_shot = 0;
  }
}

void share_got_pkt_from_server(void *csv,
                               int type,
                               unsigned char *pkt,
                               int pktlen)
{
  struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)csv;
  struct share_globreq *globreq;
  int id_pos;
  unsigned upstream_id, server_id;
  struct share_channel *chan;
  struct share_xchannel *xc;

  switch (type) {
  case SSH2_MSG_REQUEST_SUCCESS:
  case SSH2_MSG_REQUEST_FAILURE:
    globreq = cs->globreq_head;
    if (globreq->type == GLOBREQ_TCPIP_FORWARD) {
      if (type == SSH2_MSG_REQUEST_FAILURE) {
        share_remove_forwarding(cs, globreq->fwd);
      } else {
        globreq->fwd->active = TRUE;
      }
    } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) {
      if (type == SSH2_MSG_REQUEST_SUCCESS) {
        share_remove_forwarding(cs, globreq->fwd);
      }
    }
    if (globreq->want_reply) {
      send_packet_to_downstream(cs, type, pkt, pktlen, NULL);
    }
    cs->globreq_head = globreq->next;
    sfree(globreq);
    if (cs->globreq_head == NULL)
      cs->globreq_tail = NULL;

    if (!cs->sock) {
      /* Retry cleaning up this connection, in case that reply
       * was the last thing we were waiting for. */
      share_try_cleanup(cs);
    }

    break;

  case SSH2_MSG_CHANNEL_OPEN:
    id_pos = getstring_size(pkt, pktlen);
    assert(id_pos >= 0);
    server_id = GET_32BIT(pkt + id_pos);
    share_add_halfchannel(cs, server_id);

    send_packet_to_downstream(cs, type, pkt, pktlen, NULL);
    break;

  case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
  case SSH2_MSG_CHANNEL_OPEN_FAILURE:
  case SSH2_MSG_CHANNEL_CLOSE:
  case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
  case SSH2_MSG_CHANNEL_DATA:
  case SSH2_MSG_CHANNEL_EXTENDED_DATA:
  case SSH2_MSG_CHANNEL_EOF:
  case SSH2_MSG_CHANNEL_REQUEST:
  case SSH2_MSG_CHANNEL_SUCCESS:
  case SSH2_MSG_CHANNEL_FAILURE:
    /*
     * All these messages have the recipient channel id as the
     * first uint32 field in the packet. Substitute the downstream
     * channel id for our one and pass the packet downstream.
     */
    assert(pktlen >= 4);
    upstream_id = GET_32BIT(pkt);
    if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) {
      /*
       * The normal case: this id refers to an open channel.
       */
      PUT_32BIT(pkt, chan->downstream_id);
      send_packet_to_downstream(cs, type, pkt, pktlen, chan);

      /*
       * Update the channel state, for messages that need it.
       */
      if (type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
        if (chan->state == UNACKNOWLEDGED && pktlen >= 8) {
          share_channel_set_server_id(cs, chan, GET_32BIT(pkt + 4), OPEN);
          if (!cs->sock) {
            /* Retry cleaning up this connection, so that we
             * can send an immediate CLOSE on this channel for
             * which we now know the server id. */
            share_try_cleanup(cs);
          }
        }
      } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
        ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id);
        share_remove_channel(cs, chan);
      } else if (type == SSH2_MSG_CHANNEL_CLOSE) {
        if (chan->state == SENT_CLOSE) {
          ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id);
          share_remove_channel(cs, chan);
          if (!cs->sock) {
            /* Retry cleaning up this connection, in case this
             * channel closure was the last thing we were
             * waiting for. */
            share_try_cleanup(cs);
          }
        } else {
          chan->state = RCVD_CLOSE;
        }
      }
    } else if ((xc = share_find_xchannel_by_upstream(cs, upstream_id)) !=
               NULL) {
      /*
       * The unusual case: this id refers to an xchannel. Add it
       * to the xchannel's queue.
       */
      struct share_xchannel_message *msg;

      msg = share_xchannel_add_message(xc, type, pktlen);
      memcpy(msg->data, pkt, pktlen);

      /* If the xchannel is dead, then also respond to it (which
       * may involve deleting the channel). */
      if (!xc->live)
        share_dead_xchannel_respond(cs, xc);
    }
    break;

  default:
    assert(!"This packet type should never have come from ssh.c");
    break;
  }
}

static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs,
                                          int type,
                                          unsigned char *pkt,
                                          int pktlen)
{
  char *request_name;
  struct share_forwarding *fwd;
  int id_pos;
  unsigned old_id, new_id, server_id;
  struct share_globreq *globreq;
  struct share_channel *chan;
  struct share_halfchannel *hc;
  struct share_xchannel *xc;
  char *err = NULL;

  switch (type) {
  case SSH2_MSG_DISCONNECT:
    /*
     * This message stops here: if downstream is disconnecting
     * from us, that doesn't mean we want to disconnect from the
     * SSH server. Close the downstream connection and start
     * cleanup.
     */
    share_begin_cleanup(cs);
    break;

  case SSH2_MSG_GLOBAL_REQUEST:
    /*
     * The only global requests we understand are "tcpip-forward"
     * and "cancel-tcpip-forward". Since those require us to
     * maintain state, we must assume that other global requests
     * will probably require that too, and so we don't forward on
     * any request we don't understand.
     */
    request_name = getstring(pkt, pktlen);
    if (request_name == NULL) {
      err = dupprintf("Truncated GLOBAL_REQUEST packet");
      goto confused;
    }

    if (!strcmp(request_name, "tcpip-forward")) {
      int wantreplypos, orig_wantreply, port, ret;
      char *host;

      sfree(request_name);

      /*
       * Pick the packet apart to find the want_reply field and
       * the host/port we're going to ask to listen on.
       */
      wantreplypos = getstring_size(pkt, pktlen);
      if (wantreplypos < 0 || wantreplypos >= pktlen) {
        err = dupprintf("Truncated GLOBAL_REQUEST packet");
        goto confused;
      }
      orig_wantreply = pkt[wantreplypos];
      port =
          getstring_size(pkt + (wantreplypos + 1), pktlen - (wantreplypos + 1));
      port += (wantreplypos + 1);
      if (port < 0 || port > pktlen - 4) {
        err = dupprintf("Truncated GLOBAL_REQUEST packet");
        goto confused;
      }
      host = getstring(pkt + (wantreplypos + 1), pktlen - (wantreplypos + 1));
      assert(host != NULL);
      port = GET_32BIT(pkt + port);

      /*
       * See if we can allocate space in ssh.c's tree of remote
       * port forwardings. If we can't, it's because another
       * client sharing this connection has already allocated
       * the identical port forwarding, so we take it on
       * ourselves to manufacture a failure packet and send it
       * back to downstream.
       */
      ret = ssh_alloc_sharing_rportfwd(cs->parent->ssh, host, port, cs);
      if (!ret) {
        if (orig_wantreply) {
          send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, "", 0, NULL);
        }
      } else {
        /*
         * We've managed to make space for this forwarding
         * locally. Pass the request on to the SSH server, but
         * set want_reply even if it wasn't originally set, so
         * that we know whether this forwarding needs to be
         * cleaned up if downstream goes away.
         */
        int old_wantreply = pkt[wantreplypos];
        pkt[wantreplypos] = 1;
        ssh_send_packet_from_downstream(
            cs->parent->ssh,
            cs->id,
            type,
            pkt,
            pktlen,
            old_wantreply ? NULL : "upstream added want_reply flag");
        fwd = share_add_forwarding(cs, host, port);
        ssh_sharing_queue_global_request(cs->parent->ssh, cs);

        if (fwd) {
          globreq = snew(struct share_globreq);
          globreq->next = NULL;
          if (cs->globreq_tail)
            cs->globreq_tail->next = globreq;
          else
            cs->globreq_head = globreq;
          globreq->fwd = fwd;
          globreq->want_reply = orig_wantreply;
          globreq->type = GLOBREQ_TCPIP_FORWARD;
        }
      }

      sfree(host);
    } else if (!strcmp(request_name, "cancel-tcpip-forward")) {
      int wantreplypos, orig_wantreply, port;
      char *host;
      struct share_forwarding *fwd;

      sfree(request_name);

      /*
       * Pick the packet apart to find the want_reply field and
       * the host/port we're going to ask to listen on.
       */
      wantreplypos = getstring_size(pkt, pktlen);
      if (wantreplypos < 0 || wantreplypos >= pktlen) {
        err = dupprintf("Truncated GLOBAL_REQUEST packet");
        goto confused;
      }
      orig_wantreply = pkt[wantreplypos];
      port =
          getstring_size(pkt + (wantreplypos + 1), pktlen - (wantreplypos + 1));
      port += (wantreplypos + 1);
      if (port < 0 || port > pktlen - 4) {
        err = dupprintf("Truncated GLOBAL_REQUEST packet");
        goto confused;
      }
      host = getstring(pkt + (wantreplypos + 1), pktlen - (wantreplypos + 1));
      assert(host != NULL);
      port = GET_32BIT(pkt + port);

      /*
       * Look up the existing forwarding with these details.
       */
      fwd = share_find_forwarding(cs, host, port);
      if (!fwd) {
        if (orig_wantreply) {
          send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, "", 0, NULL);
        }
      } else {
        /*
         * Pass the cancel request on to the SSH server, but
         * set want_reply even if it wasn't originally set, so
         * that _we_ know whether the forwarding has been
         * deleted even if downstream doesn't want to know.
         */
        int old_wantreply = pkt[wantreplypos];
        pkt[wantreplypos] = 1;
        ssh_send_packet_from_downstream(
            cs->parent->ssh,
            cs->id,
            type,
            pkt,
            pktlen,
            old_wantreply ? NULL : "upstream added want_reply flag");
        ssh_sharing_queue_global_request(cs->parent->ssh, cs);
      }

      sfree(host);
    } else {
      /*
       * Request we don't understand. Manufacture a failure
       * message if an answer was required.
       */
      int wantreplypos;

      sfree(request_name);

      wantreplypos = getstring_size(pkt, pktlen);
      if (wantreplypos < 0 || wantreplypos >= pktlen) {
        err = dupprintf("Truncated GLOBAL_REQUEST packet");
        goto confused;
      }
      if (pkt[wantreplypos])
        send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE, "", 0, NULL);
    }
    break;

  case SSH2_MSG_CHANNEL_OPEN:
    /* Sender channel id comes after the channel type string */
    id_pos = getstring_size(pkt, pktlen);
    if (id_pos < 0 || id_pos > pktlen - 12) {
      err = dupprintf("Truncated CHANNEL_OPEN packet");
      goto confused;
    }

    old_id = GET_32BIT(pkt + id_pos);
    new_id = ssh_alloc_sharing_channel(cs->parent->ssh, cs);
    share_add_channel(
        cs, old_id, new_id, 0, UNACKNOWLEDGED, GET_32BIT(pkt + id_pos + 8));
    PUT_32BIT(pkt + id_pos, new_id);
    ssh_send_packet_from_downstream(
        cs->parent->ssh, cs->id, type, pkt, pktlen, NULL);
    break;

  case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
    if (pktlen < 16) {
      err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet");
      goto confused;
    }

    id_pos = 4; /* sender channel id is 2nd uint32 field in packet */
    old_id = GET_32BIT(pkt + id_pos);

    server_id = GET_32BIT(pkt);
    /* This server id may refer to either a halfchannel or an xchannel. */
    hc = NULL, xc = NULL; /* placate optimiser */
    if ((hc = share_find_halfchannel(cs, server_id)) != NULL) {
      new_id = ssh_alloc_sharing_channel(cs->parent->ssh, cs);
    } else if ((xc = share_find_xchannel_by_server(cs, server_id)) != NULL) {
      new_id = xc->upstream_id;
    } else {
      err =
          dupprintf("CHANNEL_OPEN_CONFIRMATION packet cited unknown channel %u",
                    (unsigned)server_id);
      goto confused;
    }

    PUT_32BIT(pkt + id_pos, new_id);

    chan = share_add_channel(
        cs, old_id, new_id, server_id, OPEN, GET_32BIT(pkt + 12));

    if (hc) {
      ssh_send_packet_from_downstream(
          cs->parent->ssh, cs->id, type, pkt, pktlen, NULL);
      share_remove_halfchannel(cs, hc);
    } else if (xc) {
      unsigned downstream_window = GET_32BIT(pkt + 8);
      if (downstream_window < 256) {
        err = dupprintf(
            "Initial window size for x11 channel must be at least 256 (got %u)",
            downstream_window);
        goto confused;
      }
      share_xchannel_confirmation(cs, xc, chan, downstream_window);
      share_remove_xchannel(cs, xc);
    }

    break;

  case SSH2_MSG_CHANNEL_OPEN_FAILURE:
    if (pktlen < 4) {
      err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet");
      goto confused;
    }

    server_id = GET_32BIT(pkt);
    /* This server id may refer to either a halfchannel or an xchannel. */
    if ((hc = share_find_halfchannel(cs, server_id)) != NULL) {
      ssh_send_packet_from_downstream(
          cs->parent->ssh, cs->id, type, pkt, pktlen, NULL);
      share_remove_halfchannel(cs, hc);
    } else if ((xc = share_find_xchannel_by_server(cs, server_id)) != NULL) {
      share_xchannel_failure(cs, xc);
    } else {
      err = dupprintf("CHANNEL_OPEN_FAILURE packet cited unknown channel %u",
                      (unsigned)server_id);
      goto confused;
    }

    break;

  case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
  case SSH2_MSG_CHANNEL_DATA:
  case SSH2_MSG_CHANNEL_EXTENDED_DATA:
  case SSH2_MSG_CHANNEL_EOF:
  case SSH2_MSG_CHANNEL_CLOSE:
  case SSH2_MSG_CHANNEL_REQUEST:
  case SSH2_MSG_CHANNEL_SUCCESS:
  case SSH2_MSG_CHANNEL_FAILURE:
  case SSH2_MSG_IGNORE:
  case SSH2_MSG_DEBUG:
    if (type == SSH2_MSG_CHANNEL_REQUEST &&
        (request_name = getstring(pkt + 4, pktlen - 4)) != NULL) {
      /*
       * Agent forwarding requests from downstream are treated
       * specially. Because OpenSSHD doesn't let us enable agent
       * forwarding independently per session channel, and in
       * particular because the OpenSSH-defined agent forwarding
       * protocol does not mark agent-channel requests with the
       * id of the session channel they originate from, the only
       * way we can implement agent forwarding in a
       * connection-shared PuTTY is to forward the _upstream_
       * agent. Hence, we unilaterally deny agent forwarding
       * requests from downstreams if we aren't prepared to
       * forward an agent ourselves.
       *
       * (If we are, then we dutifully pass agent forwarding
       * requests upstream. OpenSSHD has the curious behaviour
       * that all but the first such request will be rejected,
       * but all session channels opened after the first request
       * get agent forwarding enabled whether they ask for it or
       * not; but that's not our concern, since other SSH
       * servers supporting the same piece of protocol might in
       * principle at least manage to enable agent forwarding on
       * precisely the channels that requested it, even if the
       * subsequent CHANNEL_OPENs still can't be associated with
       * a parent session channel.)
       */
      if (!strcmp(request_name, "auth-agent-req@openssh.com") &&
          !ssh_agent_forwarding_permitted(cs->parent->ssh)) {
        unsigned server_id = GET_32BIT(pkt);
        unsigned char recipient_id[4];

        sfree(request_name);

        chan = share_find_channel_by_server(cs, server_id);
        if (chan) {
          PUT_32BIT(recipient_id, chan->downstream_id);
          send_packet_to_downstream(
              cs, SSH2_MSG_CHANNEL_FAILURE, recipient_id, 4, NULL);
        } else {
          char *buf = dupprintf("Agent forwarding request for "
                                "unrecognised channel %u",
                                server_id);
          share_disconnect(cs, buf);
          sfree(buf);
          return;
        }
        break;
      }

      /*
       * Another thing we treat specially is X11 forwarding
       * requests. For these, we have to make up another set of
       * X11 auth data, and enter it into our SSH connection's
       * list of possible X11 authorisation credentials so that
       * when we see an X11 channel open request we can know
       * whether it's one to handle locally or one to pass on to
       * a downstream, and if the latter, which one.
       */
      if (!strcmp(request_name, "x11-req")) {
        unsigned server_id = GET_32BIT(pkt);
        int want_reply, single_connection, screen;
        char *auth_proto_str, *auth_data;
        int auth_proto, protolen, datalen;
        int pos;

        sfree(request_name);

        chan = share_find_channel_by_server(cs, server_id);
        if (!chan) {
          char *buf = dupprintf("X11 forwarding request for "
                                "unrecognised channel %u",
                                server_id);
          share_disconnect(cs, buf);
          sfree(buf);
          return;
        }

        /*
         * Pick apart the whole message to find the downstream
         * auth details.
         */
        /* we have already seen: 4 bytes channel id, 4+7 request name */
        if (pktlen < 17) {
          err = dupprintf("Truncated CHANNEL_REQUEST(\"x11\") packet");
          goto confused;
        }
        want_reply = pkt[15] != 0;
        single_connection = pkt[16] != 0;
        auth_proto_str = getstring(pkt + 17, pktlen - 17);
        auth_proto = x11_identify_auth_proto(auth_proto_str);
        sfree(auth_proto_str);
        pos = 17 + getstring_size(pkt + 17, pktlen - 17);
        auth_data = getstring(pkt + pos, pktlen - pos);
        pos += getstring_size(pkt + pos, pktlen - pos);

        if (pktlen < pos + 4) {
          err = dupprintf("Truncated CHANNEL_REQUEST(\"x11\") packet");
          sfree(auth_data);
          goto confused;
        }
        screen = GET_32BIT(pkt + pos);

        if (auth_proto < 0) {
          /* Reject due to not understanding downstream's
           * requested authorisation method. */
          unsigned char recipient_id[4];
          PUT_32BIT(recipient_id, chan->downstream_id);
          send_packet_to_downstream(
              cs, SSH2_MSG_CHANNEL_FAILURE, recipient_id, 4, NULL);
          sfree(auth_data);
          break;
        }

        chan->x11_auth_proto = auth_proto;
        chan->x11_auth_data = x11_dehexify(auth_data, &chan->x11_auth_datalen);
        sfree(auth_data);
        chan->x11_auth_upstream =
            ssh_sharing_add_x11_display(cs->parent->ssh, auth_proto, cs, chan);
        chan->x11_one_shot = single_connection;

        /*
         * Now construct a replacement X forwarding request,
         * containing our own auth data, and send that to the
         * server.
         */
        protolen = strlen(chan->x11_auth_upstream->protoname);
        datalen = strlen(chan->x11_auth_upstream->datastring);
        pktlen = 29 + protolen + datalen;
        pkt = snewn(pktlen, unsigned char);
        PUT_32BIT(pkt, server_id);
        PUT_32BIT(pkt + 4, 7); /* strlen("x11-req") */
        memcpy(pkt + 8, "x11-req", 7);
        pkt[15] = want_reply;
        pkt[16] = single_connection;
        PUT_32BIT(pkt + 17, protolen);
        memcpy(pkt + 21, chan->x11_auth_upstream->protoname, protolen);
        PUT_32BIT(pkt + 21 + protolen, datalen);
        memcpy(
            pkt + 25 + protolen, chan->x11_auth_upstream->datastring, datalen);
        PUT_32BIT(pkt + 25 + protolen + datalen, screen);
        ssh_send_packet_from_downstream(cs->parent->ssh,
                                        cs->id,
                                        SSH2_MSG_CHANNEL_REQUEST,
                                        pkt,
                                        pktlen,
                                        NULL);
        sfree(pkt);

        break;
      }

      sfree(request_name);
    }

    ssh_send_packet_from_downstream(
        cs->parent->ssh, cs->id, type, pkt, pktlen, NULL);
    if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) {
      server_id = GET_32BIT(pkt);
      chan = share_find_channel_by_server(cs, server_id);
      if (chan) {
        if (chan->state == RCVD_CLOSE) {
          ssh_delete_sharing_channel(cs->parent->ssh, chan->upstream_id);
          share_remove_channel(cs, chan);
        } else {
          chan->state = SENT_CLOSE;
        }
      }
    }
    break;

  default:
    err = dupprintf("Unexpected packet type %d\n", type);
    goto confused;

    /*
     * Any other packet type is unexpected. In particular, we
     * never pass GLOBAL_REQUESTs downstream, so we never expect
     * to see SSH2_MSG_REQUEST_{SUCCESS,FAILURE}.
     */
  confused:
    assert(err != NULL);
    share_disconnect(cs, err);
    sfree(err);
    break;
  }
}

/*
 * Coroutine macros similar to, but simplified from, those in ssh.c.
 */
#define crBegin(v)                                                             \
  {                                                                            \
    int *crLine = &v;                                                          \
    switch (v) {                                                               \
    case 0:;
#define crFinishV                                                              \
  }                                                                            \
  *crLine = 0;                                                                 \
  return;                                                                      \
  }
#define crGetChar(c)                                                           \
  do {                                                                         \
    while (len == 0) {                                                         \
      *crLine = __LINE__;                                                      \
      return;                                                                  \
    case __LINE__:;                                                            \
    }                                                                          \
    len--;                                                                     \
    (c) = (unsigned char)*data++;                                              \
  } while (0)

static void share_receive(Plug plug, int urgent, char *data, int len)
{
  struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug;
  static const char expected_verstring_prefix[] =
      "SSHCONNECTION@putty.projects.tartarus.org-2.0-";
  unsigned char c;

  crBegin(cs->crLine);

  /*
   * First read the version string from downstream.
   */
  cs->recvlen = 0;
  while (1) {
    crGetChar(c);
    if (c == '\012')
      break;
    if (cs->recvlen >= sizeof(cs->recvbuf)) {
      char *buf = dupprintf("Version string far too long\n");
      share_disconnect(cs, buf);
      sfree(buf);
      goto dead;
    }
    cs->recvbuf[cs->recvlen++] = c;
  }

  /*
   * Now parse the version string to make sure it's at least vaguely
   * sensible, and log it.
   */
  if (cs->recvlen < sizeof(expected_verstring_prefix) - 1 ||
      memcmp(cs->recvbuf,
             expected_verstring_prefix,
             sizeof(expected_verstring_prefix) - 1)) {
    char *buf = dupprintf("Version string did not have expected prefix\n");
    share_disconnect(cs, buf);
    sfree(buf);
    goto dead;
  }
  if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen - 1] == '\015')
    cs->recvlen--; /* trim off \r before \n */
  ssh_sharing_logf(cs->parent->ssh,
                   cs->id,
                   "Downstream version string: %.*s",
                   cs->recvlen,
                   cs->recvbuf);
  cs->got_verstring = TRUE;

  /*
   * Loop round reading packets.
   */
  while (1) {
    cs->recvlen = 0;
    while (cs->recvlen < 4) {
      crGetChar(c);
      cs->recvbuf[cs->recvlen++] = c;
    }
    cs->curr_packetlen = toint(GET_32BIT(cs->recvbuf) + 4);
    if (cs->curr_packetlen < 5 || cs->curr_packetlen > sizeof(cs->recvbuf)) {
      char *buf =
          dupprintf("Bad packet length %u\n", (unsigned)cs->curr_packetlen);
      share_disconnect(cs, buf);
      sfree(buf);
      goto dead;
    }
    while (cs->recvlen < cs->curr_packetlen) {
      crGetChar(c);
      cs->recvbuf[cs->recvlen++] = c;
    }

    share_got_pkt_from_downstream(
        cs, cs->recvbuf[4], cs->recvbuf + 5, cs->recvlen - 5);
  }

dead:;
  crFinishV;
}

static void share_sent(Plug plug, int bufsize)
{
  /* struct ssh_sharing_connstate *cs = (struct ssh_sharing_connstate *)plug; */

  /*
   * We do nothing here, because we expect that there won't be a
   * need to throttle and unthrottle the connection to a downstream.
   * It should automatically throttle itself: if the SSH server
   * sends huge amounts of data on all channels then it'll run out
   * of window until our downstream sends it back some
   * WINDOW_ADJUSTs.
   */
}

static void share_listen_closing(Plug plug,
                                 const char *error_msg,
                                 int error_code,
                                 int calling_back)
{
  struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug;
  if (error_msg)
    ssh_sharing_logf(sharestate->ssh, 0, "listening socket: %s", error_msg);
  sk_close(sharestate->listensock);
  sharestate->listensock = NULL;
}

static void share_send_verstring(struct ssh_sharing_connstate *cs)
{
  char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-",
                            cs->parent->server_verstring,
                            "\015\012",
                            NULL);
  sk_write(cs->sock, fullstring, strlen(fullstring));
  sfree(fullstring);

  cs->sent_verstring = TRUE;
}

int share_ndownstreams(void *state)
{
  struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)state;
  return count234(sharestate->connections);
}

void share_activate(void *state, const char *server_verstring)
{
  /*
   * Indication from ssh.c that we are now ready to begin serving
   * any downstreams that have already connected to us.
   */
  struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)state;
  struct ssh_sharing_connstate *cs;
  int i;

  /*
   * Trim the server's version string down to just the software
   * version component, removing "SSH-2.0-" or whatever at the
   * front.
   */
  for (i = 0; i < 2; i++) {
    server_verstring += strcspn(server_verstring, "-");
    if (*server_verstring)
      server_verstring++;
  }

  sharestate->server_verstring = dupstr(server_verstring);

  for (i = 0; (cs = (struct ssh_sharing_connstate *)index234(
                   sharestate->connections, i)) != NULL;
       i++) {
    assert(!cs->sent_verstring);
    share_send_verstring(cs);
  }
}

static int share_listen_accepting(Plug plug,
                                  accept_fn_t constructor,
                                  accept_ctx_t ctx)
{
  static const struct plug_function_table connection_fn_table = {
      NULL, /* no log function, because that's for outgoing connections */
      share_closing,
      share_receive,
      share_sent,
      NULL /* no accepting function, because we've already done it */
  };
  struct ssh_sharing_state *sharestate = (struct ssh_sharing_state *)plug;
  struct ssh_sharing_connstate *cs;
  const char *err;
  char *peerinfo;

  /*
   * A new downstream has connected to us.
   */
  cs = snew(struct ssh_sharing_connstate);
  cs->fn = &connection_fn_table;
  cs->parent = sharestate;

  if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 &&
      (cs->id = share_find_unused_id(sharestate, 1)) == 0) {
    sfree(cs);
    return 1;
  }
  sharestate->nextid = cs->id + 1;
  if (sharestate->nextid == 0)
    sharestate->nextid++; /* only happens in VERY long-running upstreams */

  cs->sock = constructor(ctx, (Plug)cs);
  if ((err = sk_socket_error(cs->sock)) != NULL) {
    sfree(cs);
    return err != NULL;
  }

  sk_set_frozen(cs->sock, 0);

  add234(cs->parent->connections, cs);

  cs->sent_verstring = FALSE;
  if (sharestate->server_verstring)
    share_send_verstring(cs);

  cs->got_verstring = FALSE;
  cs->recvlen = 0;
  cs->crLine = 0;
  cs->halfchannels = newtree234(share_halfchannel_cmp);
  cs->channels_by_us = newtree234(share_channel_us_cmp);
  cs->channels_by_server = newtree234(share_channel_server_cmp);
  cs->xchannels_by_us = newtree234(share_xchannel_us_cmp);
  cs->xchannels_by_server = newtree234(share_xchannel_server_cmp);
  cs->forwardings = newtree234(share_forwarding_cmp);
  cs->globreq_head = cs->globreq_tail = NULL;

  peerinfo = sk_peer_info(cs->sock);
  ssh_sharing_downstream_connected(sharestate->ssh, cs->id, peerinfo);
  sfree(peerinfo);

  return 0;
}

/* Per-application overrides for what roles we can take (e.g. pscp
 * will never be an upstream) */
extern const int share_can_be_downstream;
extern const int share_can_be_upstream;

/*
 * Decide on the string used to identify the connection point between
 * upstream and downstream (be it a Windows named pipe or a
 * Unix-domain socket or whatever else).
 *
 * I wondered about making this a SHA hash of all sorts of pieces of
 * the PuTTY configuration - essentially everything PuTTY uses to know
 * where and how to make a connection, including all the proxy details
 * (or rather, all the _relevant_ ones - only including settings that
 * other settings didn't prevent from having any effect), plus the
 * username. However, I think it's better to keep it really simple:
 * the connection point identifier is derived from the hostname and
 * port used to index the host-key cache (not necessarily where we
 * _physically_ connected to, in cases involving proxies or
 * CONF_loghost), plus the username if one is specified.
 *
 * The per-platform code will quite likely hash or obfuscate this name
 * in turn, for privacy from other users; failing that, it might
 * transform it to avoid dangerous filename characters and so on. But
 * that doesn't matter to us: for us, the point is that two session
 * configurations which return the same string from this function will
 * be treated as potentially shareable with each other.
 */
char *ssh_share_sockname(const char *host, int port, Conf *conf)
{
  char *username = get_remote_username(conf);
  char *sockname;

  if (port == 22) {
    if (username)
      sockname = dupprintf("%s@%s", username, host);
    else
      sockname = dupprintf("%s", host);
  } else {
    if (username)
      sockname = dupprintf("%s@%s:%d", username, host, port);
    else
      sockname = dupprintf("%s:%d", host, port);
  }

  sfree(username);
  return sockname;
}

static void nullplug_socket_log(Plug plug,
                                int type,
                                SockAddr addr,
                                int port,
                                const char *error_msg,
                                int error_code)
{
}
static void nullplug_closing(Plug plug,
                             const char *error_msg,
                             int error_code,
                             int calling_back)
{
}
static void nullplug_receive(Plug plug, int urgent, char *data, int len)
{
}
static void nullplug_sent(Plug plug, int bufsize)
{
}

int ssh_share_test_for_upstream(const char *host, int port, Conf *conf)
{
  static const struct plug_function_table fn_table = {nullplug_socket_log,
                                                      nullplug_closing,
                                                      nullplug_receive,
                                                      nullplug_sent,
                                                      NULL};
  struct nullplug {
    const struct plug_function_table *fn;
  } np;

  char *sockname, *logtext, *ds_err, *us_err;
  int result;
  Socket sock;

  np.fn = &fn_table;

  sockname = ssh_share_sockname(host, port, conf);

  sock = NULL;
  logtext = ds_err = us_err = NULL;
  result = platform_ssh_share(sockname,
                              conf,
                              (Plug)&np,
                              (Plug)NULL,
                              &sock,
                              &logtext,
                              &ds_err,
                              &us_err,
                              FALSE,
                              TRUE);

  sfree(logtext);
  sfree(ds_err);
  sfree(us_err);
  sfree(sockname);

  if (result == SHARE_NONE) {
    assert(sock == NULL);
    return FALSE;
  } else {
    assert(result == SHARE_DOWNSTREAM);
    sk_close(sock);
    return TRUE;
  }
}

/*
 * Init function for connection sharing. We either open a listening
 * socket and become an upstream, or connect to an existing one and
 * become a downstream, or do neither. We are responsible for deciding
 * which of these to do (including checking the Conf to see if
 * connection sharing is even enabled in the first place). If we
 * become a downstream, we return the Socket with which we connected
 * to the upstream; otherwise (whether or not we have established an
 * upstream) we return NULL.
 */
Socket ssh_connection_sharing_init(
    const char *host, int port, Conf *conf, Ssh ssh, void **state)
{
  static const struct plug_function_table listen_fn_table = {
      NULL, /* no log function, because that's for outgoing connections */
      share_listen_closing,
      NULL, /* no receive function on a listening socket */
      NULL, /* no sent function on a listening socket */
      share_listen_accepting};

  int result, can_upstream, can_downstream;
  char *logtext, *ds_err, *us_err;
  char *sockname;
  Socket sock;
  struct ssh_sharing_state *sharestate;

  if (!conf_get_int(conf, CONF_ssh_connection_sharing))
    return NULL; /* do not share anything */
  can_upstream = share_can_be_upstream &&
                 conf_get_int(conf, CONF_ssh_connection_sharing_upstream);
  can_downstream = share_can_be_downstream &&
                   conf_get_int(conf, CONF_ssh_connection_sharing_downstream);
  if (!can_upstream && !can_downstream)
    return NULL;

  sockname = ssh_share_sockname(host, port, conf);

  /*
   * Create a data structure for the listening plug if we turn out
   * to be an upstream.
   */
  sharestate = snew(struct ssh_sharing_state);
  sharestate->fn = &listen_fn_table;
  sharestate->listensock = NULL;

  /*
   * Now hand off to a per-platform routine that either connects to
   * an existing upstream (using 'ssh' as the plug), establishes our
   * own upstream (using 'sharestate' as the plug), or forks off a
   * separate upstream and then connects to that. It will return a
   * code telling us which kind of socket it put in 'sock'.
   */
  sock = NULL;
  logtext = ds_err = us_err = NULL;
  result = platform_ssh_share(sockname,
                              conf,
                              (Plug)ssh,
                              (Plug)sharestate,
                              &sock,
                              &logtext,
                              &ds_err,
                              &us_err,
                              can_upstream,
                              can_downstream);
  ssh_connshare_log(ssh, result, logtext, ds_err, us_err);
  sfree(logtext);
  sfree(ds_err);
  sfree(us_err);
  switch (result) {
  case SHARE_NONE:
    /*
     * We aren't sharing our connection at all (e.g. something
     * went wrong setting the socket up). Free the upstream
     * structure and return NULL.
     */
    assert(sock == NULL);
    *state = NULL;
    sfree(sharestate);
    sfree(sockname);
    return NULL;

  case SHARE_DOWNSTREAM:
    /*
     * We are downstream, so free sharestate which it turns out we
     * don't need after all, and return the downstream socket as a
     * replacement for an ordinary SSH connection.
     */
    *state = NULL;
    sfree(sharestate);
    sfree(sockname);
    return sock;

  case SHARE_UPSTREAM:
    /*
     * We are upstream. Set up sharestate properly and pass a copy
     * to the caller; return NULL, to tell ssh.c that it has to
     * make an ordinary connection after all.
     */
    *state = sharestate;
    sharestate->listensock = sock;
    sharestate->connections = newtree234(share_connstate_cmp);
    sharestate->ssh = ssh;
    sharestate->server_verstring = NULL;
    sharestate->sockname = sockname;
    sharestate->nextid = 1;
    return NULL;
  }

  return NULL;
}
